﻿using System.Text;
using CppAst;

namespace ast;

public class CWrapperGenerator
{
    public static string GetCppWrapperFunctionSignature(ModuleInfo moduleInfo, string moduleName, ClassInfo? classInfo,
        FunctionInfo functionInfo, int funIndex)
    {
        var isConstructor = functionInfo.Function.IsConstructor;
        var _ = new StringBuilder();
        if (isConstructor) classInfo = null;


        var argsBuilder = new StringBuilder();
        argsBuilder.Append(
            $"{GetCppWrapperFunctionName(moduleInfo, moduleName, classInfo, functionInfo, funIndex)}(");
        var isFirstArg = true;
        if (classInfo != null)
        {
            isFirstArg = false;
            if (functionInfo.Function.IsConst)
            {
                argsBuilder.Append(
                    $"{GetCppParaAndNameString(new ExportPara(new CppPointerType(new CppQualifiedType(CppTypeQualifier.Const, classInfo.ClassType)), "c_this"))}");
            }
            else
            {
                argsBuilder.Append(
                    $"{GetCppParaAndNameString(new ExportPara(new CppPointerType(classInfo.ClassType), "c_this"))}");
            }
        }

        for (var index = 0; index < functionInfo.ExportParameters.Count; index++)
        {
            var parameter = functionInfo.ExportParameters[index];
            if (!isFirstArg)
                argsBuilder.Append(",");
            else
                isFirstArg = false;

            argsBuilder.Append($"{GetCppParaAndNameString(parameter)}");
        }

        argsBuilder.Append(")");

        var functionReturnType = functionInfo.ExportReturnType.SourceType;
        if (!ExportConfig.FunctionReturnVoid(functionReturnType))
        {
            var funcSign = GetCppParaAndNameString(new ExportPara(functionInfo.ExportReturnType, argsBuilder.ToString()));
            return funcSign;
        }

        return "void " + argsBuilder;
    }

    public static string GetCppWrapperFunctionName(ModuleInfo moduleInfo, string moduleName,
        ClassInfo? classInfo, FunctionInfo functionInfo, int funcIndex)
    {
        if (moduleName == "") moduleName = "default";

        var cFunctionName = moduleName;
        if (classInfo != null)
        {
            cFunctionName += "_" + classInfo.ClassType.FullName;
        }

        var realName = functionInfo.Function.Name;
        if (funcIndex != 0)
        {
            realName += funcIndex + 10;
        }

        if (functionInfo.Function.IsConstructor)
        {
            realName = "New" + realName;
        }

        cFunctionName += "_" + realName;
        cFunctionName = cFunctionName.Replace("::", "_");
        cFunctionName = cFunctionName.Replace("<", "_");
        cFunctionName = cFunctionName.Replace(">", "");
        cFunctionName = cFunctionName.Replace(",", "_");
        cFunctionName = cFunctionName.Replace(" ", "");
        cFunctionName = cFunctionName.Replace("*", "Ptr");
        cFunctionName = cFunctionName.Replace("&", "Ref");
        cFunctionName = cFunctionName.Replace("-", "");
        return cFunctionName;
    }

    public static string GetWrapCallRawCppFunctionBody(ClassInfo? classInfo, FunctionInfo functionInfo)
    {
        if (functionInfo.Function.IsConstructor) classInfo = null;
        var isDeConstructor = functionInfo.Function.IsDestructor;
        if (isDeConstructor)
        {
            return "\tif(c_this==0){\n\t    return;\n\t}\n\tdelete c_this;\n";
        }

        if (functionInfo.Function.IsNewArray)
        {
            var functionReturnType = functionInfo.Function.ReturnType as CppPointerType;

            var elementTypeFullName = functionReturnType.ElementType.FullName;
            return
                $"\t{elementTypeFullName}* c_this=new {elementTypeFullName}[n];\n\treturn c_this;\n";
        }

        if (functionInfo.Function.IsDeleteArray)
        {
            return "\tif(c_this==0){\n\t\treturn;\n\t}\n\tdelete []c_this;\n";
        }

        if (functionInfo.Function.IsIndexArray)
        {
            return "\tif(c_this==0){\n\t\treturn 0;\n\t}\n\tc_this+=n;\n\treturn c_this;\n";
        }

        if (functionInfo.Function.IsRefGetter)
        {
            return
                $"\treturn  ({GetCppParaAndNameString(new ExportPara(functionInfo.ExportReturnType, ""))})(void *)(&c_this->{functionInfo.Function.PropertyName});\n";
        }

        if (functionInfo.Function.IsBitGetter)
        {
            return
                $"\treturn ({functionInfo.ExportReturnType.Type.FullName})(c_this->{functionInfo.Function.PropertyName});\n";
        }

        if (functionInfo.Function.IsRefUpdater)
        {
            var para = functionInfo.ExportParameters[0];
            return $"\tc_this->{functionInfo.Function.PropertyName}=*({GetCppParaAndNameString(new ExportPara(para,""))})(value);\n";
        }

        if (functionInfo.Function.IsBitSetter)
        {
            return $"\tc_this->{functionInfo.Function.PropertyName}=value;\n";
        }

        var builder = new StringBuilder();

        var isConstructor = functionInfo.Function.IsConstructor;
        var cppFunction = functionInfo.Function;
        var functionInfoExportParameters = functionInfo.ExportParameters;
        var exportResultType = functionInfo.ExportReturnType;
        if (isConstructor) classInfo = null;

        // if (classInfo != null) builder.Append($"\t{GetCppWrapInTypeString(classInfo.ClassType)} c_this;\n");

        for (var i = 0; i < functionInfoExportParameters.Count; i++)
        {
            var para = functionInfoExportParameters[i];
            var simplifiedType = para.Type;
            var sourceType = para.SourceType;
            if (simplifiedType is CppArrayType cppArrayType)
            {
                builder.Append(
                    $"\t{new CppParameter(new CppReferenceType(cppArrayType), $"c_arg_{para.Name}")}=*({new CppPointerType(cppArrayType)})(void*)({para.Name});\n");
            }
            else
            {
                if (simplifiedType is CppPrimitiveType or CppEnum)
                {
                    builder.Append(
                        $"\t{sourceType.FullName} c_arg_{para.Name}={para.Name};\n");
                }
                else if (simplifiedType is CppPointerType cppPointerType)
                {
                    //指针类型的可能有指针类型转换，可能有const 参与运算
                    var parameter = new CppParameter(sourceType, $"c_arg_{para.Name}");
                    builder.Append($"\t{parameter}=({sourceType.FullName})(void*)({para.Name});\n");
                }
                else if (simplifiedType is CppReferenceType cppReferenceType)
                {
                    var ptr = new CppPointerType((sourceType as CppReferenceType).ElementType);
                    var parameter = new CppParameter(sourceType, $"c_arg_{para.Name}");

                    //引用类型的使用了指针传递参数进来
                    builder.Append(
                        $"\t{parameter}=*({ptr.FullName})({para.Name});\n");
                }
                else if (simplifiedType is CppClass cppClass)
                {
                    //使用了对象类型，此处需要先解指针
                    builder.Append(
                        $"\t{sourceType.FullName}& c_arg_{para.Name}=*{para.Name};\n");
                }

                else
                {
                    builder.Append(
                        $"\t{sourceType.FullName} c_arg_{para.Name}=({sourceType.FullName})(void*)({para.Name});\n");
                }
            }
        }

        var hasReturnResult = !ExportConfig.FunctionReturnVoid(exportResultType.Type);
        if (hasReturnResult)
        {
            if (!string.IsNullOrEmpty(cppFunction.PropertyName))
            {
            }
            else if (isConstructor)
            {
                builder.Append($"\t{exportResultType.SourceType.FullName}* wrap_out = ");
            }
            else
            {
                builder.Append($"\t{exportResultType.SourceType.FullName} c_out = ");
            }
        }
        else
        {
            builder.Append("\t");
        }

        if (!string.IsNullOrEmpty(cppFunction.PropertyName))
        {
        }
        else
        {
            if (isConstructor)
            {
                builder.Append($"new {exportResultType.Type.FullName}(");
            }
            else
            {
                if (classInfo != null)
                    builder.Append($"c_this->{cppFunction.Name}(");
                else
                    builder.Append($"{cppFunction.FullName}(");
            }

            for (var i = 0; i < functionInfoExportParameters.Count; i++)
            {
                var para = functionInfoExportParameters[i];
                if (i != 0) builder.Append(",");

                builder.Append($"c_arg_{para.Name}");
            }

            builder.Append(");\n");
        }

        if (!string.IsNullOrEmpty(cppFunction.PropertyName))
        {
            if (cppFunction.IsGetter)
            {
                builder.Append(
                    $"\treturn ({GetCppParaAndNameString(new ExportPara(exportResultType, ""))})(&(c_this->{cppFunction.PropertyName}));\n");
            }
        }
        else if (hasReturnResult)
        {
            if (isConstructor)
            {
                builder.Append("\treturn wrap_out;\n");
            }
            else
            {
                builder.Append(
                    $"\t{GetCppParaAndNameString(new ExportPara(exportResultType, "wrap_out"))}  = {CppRawOutToCppWrapOut(exportResultType)};\n");
                builder.AppendFormat("\treturn wrap_out;\n");
            }
        }

        return builder.ToString();
    }


    public static string GetCppParaAndNameString(ExportPara para)
    {
        switch (para.Type)
        {
            case CppReferenceType cppReferenceType:
                var replacedSrcType = ClassUtil.ReplaceFirstRefToPointer(para.SourceType);
                return GetCppParaAndNameString(new ExportPara(replacedSrcType, para.Name));
            case CppArrayType cppArrayType:
                if (cppArrayType.Size == -1)
                {
                    return ClassUtil.TypeToString(para.SourceType, para.Name);
                }
                else
                {
                    var replacedType = new CppPointerType(para.SourceType);
                    return GetCppParaAndNameString(new ExportPara(replacedType, para.Name));
                }

            case CppClass cppClassType:
                return GetCppParaAndNameString(new ExportPara(new CppPointerType(cppClassType), para.Name));

            default:
                return ClassUtil.TypeToString(para.SourceType, para.Name);
        }
    }


    //
    // public static string GetCppWrapTypeString(CppType cppType)
    // {
    //     switch (cppType)
    //     {
    //         case CppPointerType cppPointerType:
    //             return GetCppWrapInPointerTypeString(cppPointerType);
    //         case CppReferenceType cppReferenceType:
    //             return GetCppWrapInRefTypeString(cppReferenceType);
    //         case CppPrimitiveType cppPrimitiveType:
    //             return cppPrimitiveType.ToString();
    //         case CppEnum cppEnumType:
    //             //todo 添加枚举到go文件中
    //             return cppEnumType.FullName;
    //         case CppClass cppClassType:
    //             return cppClassType.FullName + "*";
    //         case CppArrayType cppArrayType:
    //             return GetCppWrapTypeString(new CppPointerType(cppArrayType.ElementType));
    //         case CppUnexposedType unexposedType:
    //             if (unexposedType.Name.Equals("__attribute__((__vector_size__(4 * sizeof(float)))) float"))
    //                 return "float*";
    //
    //             break;
    //     }
    //
    //     throw new Exception("wrap type not registered");
    // }

    public static string CppRawOutToCppWrapOut(ExportType resultType)
    {
        if (resultType.Type is CppEnum)
        {
            return "(c_out)";
        }

        if (resultType.Type is CppPrimitiveType cppPrimitiveType) return "(c_out)";

        if (resultType.Type is CppClass cppClass)
        {
            var coutToWrapOut = $"new {cppClass.FullName}(c_out)";
            return coutToWrapOut;
        }


        if (resultType.Type is CppReferenceType)
        {
            return $"({GetCppParaAndNameString(new ExportPara(resultType, ""))})(&c_out)";
        }

        return $"({GetCppParaAndNameString(new ExportPara(resultType, ""))})(void*)(c_out)";
    }


    // public static string GetCppWrapInRefTypeString(CppReferenceType cppPointerType)
    // {
    //     var ret = new CppPointerType(cppPointerType.ElementType);
    //     return GetCppWrapInPointerTypeString(ret);
    // }

    // public static string GetCppWrapInPointerTypeString(CppPointerType cppPointerType)
    // {
    //     if (cppPointerType.ElementType is CppPrimitiveType cppPrimitiveType)
    //         return GetCppWrapTypeString(cppPrimitiveType) + "*";
    //     if (cppPointerType.ElementType is CppEnum cppEnum)
    //         return GetCppWrapTypeString(cppEnum) + "*";
    //
    //     if (cppPointerType.ElementType is CppPointerType cppPointerTypeElement)
    //         return GetCppWrapTypeString(cppPointerTypeElement) + "*";
    //
    //     if (cppPointerType.ElementType is CppReferenceType cppReferenceType)
    //         return GetCppWrapTypeString(cppReferenceType.ElementType) + "*";
    //
    //     return GetCppWrapTypeString(cppPointerType.ElementType);
    // }
}